## MessageCenter - Godot消息中心
## 进程内消息转发中心，实现发布-订阅(Pub/Sub)模式
## 版本: 1.0.0
## 基于C#版MessageCenter移植

extends Node

# 订阅字典: {channel_name: [Subscription, ...]}
var _subscriptions: Dictionary = {}

# 用于生成匿名订阅者的ID
var _anonymous_id: int = 0


## 发送消息到指定频道
## @param sender: 发送者对象，不能为null
## @param channel: 频道名称，不能为空
## @param data: 要发送的数据（可选）
func send(sender, channel: String, data = null) -> void:
	if sender == null:
		push_error("MessageCenter.send: sender cannot be null")
		return
	
	if channel.is_empty():
		push_error("MessageCenter.send: channel cannot be empty")
		return
	
	_inner_send(sender, channel, data)


## 内部发送实现
func _inner_send(sender, channel: String, data) -> void:
	if not _subscriptions.has(channel):
		return
	
	var subscription_list = _subscriptions[channel]
	# 创建副本遍历，防止在回调中修改订阅列表导致问题
	var invalid_subscriptions = []
	
	for subscription in subscription_list.duplicate():
		if subscription != null:
			# 尝试调用，如果返回false说明订阅源已失效
			if not subscription.invoke(sender, data):
				invalid_subscriptions.append(subscription)
	
	# 清理已失效的订阅（订阅源对象已被销毁）
	if invalid_subscriptions.size() > 0:
		_clean_invalid_subscriptions(channel, invalid_subscriptions)


## 订阅消息频道
## @param subscriber: 订阅者对象，用于后续取消订阅
## @param channel: 频道名称
## @param callback: 回调函数，可以是Callable或函数名
## @param params_count: 回调函数参数数量 (1=只接收sender, 2=接收sender和data)
func subscribe(subscriber, channel: String, callback, params_count: int = 2) -> void:
	if subscriber == null:
		push_error("MessageCenter.subscribe: subscriber cannot be null")
		return
	
	if channel.is_empty():
		push_error("MessageCenter.subscribe: channel cannot be empty")
		return
	
	if callback == null:
		push_error("MessageCenter.subscribe: callback cannot be null")
		return
	
	_inner_subscribe(subscriber, channel, callback, params_count)


## 匿名订阅（返回订阅者对象用于取消订阅）
## @param channel: 频道名称
## @param callback: 回调函数
## @param params_count: 回调函数参数数量
## @return: 返回订阅者对象，用于后续取消订阅
func subscribe_anonymous(channel: String, callback, params_count: int = 2):
	_anonymous_id += 1
	var anonymous_subscriber = "AnonymousSubscriber_" + str(_anonymous_id)
	_inner_subscribe(anonymous_subscriber, channel, callback, params_count)
	return anonymous_subscriber


## 内部订阅实现
func _inner_subscribe(subscriber, channel: String, callback, params_count: int) -> void:
	var subscription = Subscription.new(subscriber, callback, params_count)
	
	if _subscriptions.has(channel):
		_subscriptions[channel].append(subscription)
	else:
		_subscriptions[channel] = [subscription]


## 取消订阅
## @param subscriber: 订阅者对象
## @param channel: 频道名称
func unsubscribe(subscriber, channel: String) -> void:
	if not _subscriptions.has(channel):
		return
	
	var subscription_list = _subscriptions[channel]
	
	# 修复C#版本的逻辑错误：使用 or 而非 and
	if subscription_list == null or subscription_list.size() == 0:
		_subscriptions.erase(channel)
		return
	
	# 移除该订阅者的所有订阅（使用弱引用比较）
	_subscriptions[channel] = subscription_list.filter(
		func(sub): return sub.get_source() != subscriber
	)
	
	# 如果频道没有订阅者了，移除频道
	if _subscriptions[channel].size() == 0:
		_subscriptions.erase(channel)


## 取消订阅者的所有订阅
## @param subscriber: 订阅者对象
func unsubscribe_all(subscriber) -> void:
	var channels_to_remove = []
	
	for channel in _subscriptions.keys():
		var subscription_list = _subscriptions[channel]
		_subscriptions[channel] = subscription_list.filter(
			func(sub): return sub.get_source() != subscriber
		)
		
		if _subscriptions[channel].size() == 0:
			channels_to_remove.append(channel)
	
	for channel in channels_to_remove:
		_subscriptions.erase(channel)


## 清理失效的订阅（内部方法）
func _clean_invalid_subscriptions(channel: String, invalid_subs: Array) -> void:
	if not _subscriptions.has(channel):
		return
	
	var subscription_list = _subscriptions[channel]
	
	# 移除失效的订阅
	for invalid_sub in invalid_subs:
		subscription_list.erase(invalid_sub)
	
	# 如果频道没有订阅者了，移除频道
	if subscription_list.size() == 0:
		_subscriptions.erase(channel)


## 获取频道订阅数量
## @param channel: 频道名称
## @return: 订阅者数量
func get_subscription_count(channel: String) -> int:
	if not _subscriptions.has(channel):
		return 0
	return _subscriptions[channel].size()


## 检查频道是否存在订阅
## @param channel: 频道名称
## @return: 是否有订阅
func has_subscriptions(channel: String) -> bool:
	return _subscriptions.has(channel) and _subscriptions[channel].size() > 0


## 清空所有订阅
func clear_all() -> void:
	_subscriptions.clear()


## 获取所有频道名称
## @return: 频道名称数组
func get_all_channels() -> Array:
	return _subscriptions.keys()


## 调试：打印所有订阅信息
func debug_print_subscriptions() -> void:
	print("=== MessageCenter Subscriptions ===")
	for channel in _subscriptions.keys():
		print("Channel: ", channel, " - Subscribers: ", _subscriptions[channel].size())
		for sub in _subscriptions[channel]:
			print("  - ", sub.to_string())
	print("===================================")


# ========================================
# Subscription 内部类
# ========================================
class Subscription:
	var id: String # 唯一标识符
	var source_ref: WeakRef # 订阅源对象的弱引用（避免内存泄漏）
	var callback: Callable # 回调函数
	var params_count: int # 参数数量
	
	func _init(src, cb, pcount: int = 2):
		self.id = str(Time.get_ticks_usec()) + "_" + str(randi())
		
		if src == null:
			push_error("Subscription: source cannot be null")
			return
		
		if cb == null:
			push_error("Subscription: callback cannot be null")
			return
		
		# 为字符串类型创建特殊处理（匿名订阅者）
		if src is String:
			# 字符串类型无需弱引用，直接存储
			self.source_ref = null
		else:
			# 使用弱引用存储对象，避免内存泄漏
			self.source_ref = weakref(src)
		
		self.callback = cb
		self.params_count = pcount
	
	## 获取订阅源对象
	func get_source():
		if source_ref == null:
			return null
		return source_ref.get_ref()
	
	## 检查订阅源是否仍然有效
	func is_valid() -> bool:
		if source_ref == null:
			return false
		# 检查弱引用的对象是否还存活
		return get_source() != null
	
	## 调用回调函数
	func invoke(sender, data) -> bool:
		if sender == null:
			return false
		
		# 检查订阅源是否还有效
		if not is_valid():
			return false
		
		if callback == null:
			push_error("Subscription.invoke: callback is null")
			return false
		
		# 根据参数数量调用
		if params_count == 1:
			# 只接收sender
			if callback is Callable:
				callback.call(sender)
			else:
				push_error("Subscription.invoke: invalid callback type")
				return false
		else:
			# 接收sender和data
			if callback is Callable:
				callback.call(sender, data)
			else:
				push_error("Subscription.invoke: invalid callback type")
				return false
		
		return true
	
	## 转换为字符串（调试用）
	func debug_string() -> String:
		var src = get_source()
		return "Subscription[id=%s, source=%s, valid=%s, params=%d]" % [id, src, is_valid(), params_count]
